/**
 * 20220303:loading assets with both [classic url] and [dynamic import url]...jordia
 * 20220310:simplify [asset-urls] inputting，no longer support [array-input]
 * 20220417:[this.onLoaded->this._onLoaded] when no asset needs to load
 * 20221011: rewrite with typescript, massive modified.
 */
 import {TextureLoader,CubeTextureLoader,AudioLoader,FileLoader} from 'three';
 import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader';
 
 interface LoaderInfo{
    key:string,name:any,prefix:Array<string>,postfix:Array<string|Array<string>>,
 }
 interface AssetInfo{
    id:number,
    name:string,
    url:string,
    loader:any
 }
 interface MsgHandler{
    (msg?:any):void;
 }
 interface InfoHandler{
    (info:AssetInfo,progress:number):void;
 }
 class LooseObject<T>{
    [key:string]:T;
 }
 class Loader{
    public static readonly loaders:Array<LoaderInfo> = [
            {key:'texture', name:TextureLoader,prefix:['data:image/','.'], postfix:["jpg","jpeg","svg","png","tif"]},
            {key:'gltf', name:GLTFLoader,prefix:['.'],postfix:["glb"]},
            {key:'audio', name:AudioLoader,prefix:['.'],postfix:["mp3","ogg"]},
            {key:'file', name:FileLoader,prefix:['.'],postfix:["json","txt"]},
            {key:'cubeTexture', name:CubeTextureLoader,prefix:['data:image/','.'],postfix:[["jpg","jpeg","png"]]},
        ];
    public static getLoader(key:string):LoaderInfo|null{
        for(let info of Loader.loaders){
            if(info.key===key)return info;
        }
        return null;
    }
 }
 
 /**
  * define loadable type of assets ,select useable loader,and get loaded assets;
  * use [assets-load] to load assets;
  * see [assets-load.js] to find out how to set [load-infomation] 
  */
 class AssetsLoader{
    
     private _isLoading:boolean=false;
     private _infos!:Array<AssetInfo>;
     private _loadedAssets!:Array<any>;
     private _progress!:{total:number,current:Float32Array};
     private _onLoadStart:MsgHandler|null=null;
     private _onLoading:InfoHandler|null=null;
     private _onError:MsgHandler|null=null;
     private _onLoaded:MsgHandler|null=null;

     //................................................public.......................................................
 
     /**
      * generate and return an assets-object
      */
     get assets(){
         let ast=new LooseObject<AssetInfo>();
         for(let i=0;i<this._infos.length;i++){
             const info=this._infos[i];
             ast[info.name]=this._loadedAssets[i];
         }
         return ast;
     }
 
     /**
      * set callback for load-start
      */
     set onLoadStart(func:MsgHandler){
         this._onLoadStart=func||null;
     }
 
     /**
      * set callback for load-progress 
      */
     set onLoading(func_info_prg:InfoHandler){
         this._onLoading=func_info_prg||null;
     }
 
     /**
      * set callback for load-error 
      */
     set onError(func_err:MsgHandler){
         this._onError=func_err||null;
     }
 
     /**
      * set callback for load-success 
      */
     set onLoaded(func:MsgHandler){
         this._onLoaded=func||null;
     }
 
     /**
      * (processed)url and postfix of asset
      */
     public setTargets(target:LooseObject<string>):AssetsLoader{
         this._infos=[];
         let id=-1;
         for(let key in target){
 
             const typ=this._lookupTypeString(target[key]);
             if(!typ){
                console.warn('this type is not supported:',key);
                continue;
             }
             const loaderInfo=Loader.getLoader(typ);
             if(!loaderInfo){
                 console.warn('no loader was found to handle this type of asset:',target);
                 continue;
             }
             const LoaderClass=loaderInfo.name;
             id++;
             const loader=new LoaderClass();
             this._infos.push({
                 id:id,
                 name:key,
                 url:target[key],
                 loader:loader
             });
         }
         
         return this;
     }
 
     /**
      * start loading
      */
     public startLoad():void{
         if(this._isLoading)return;
         if((!this._infos)||(!this._infos.length)){
             if(this._onLoadStart)this._onLoadStart();
             if(this._onLoaded)this._onLoaded();//20220417
             return;
         }
         this._loadedAssets=[];
         this._progress={
             total:this._infos.length,
             current:new Float32Array(this._infos.length),
         };
         if(this._onLoadStart)this._onLoadStart();
         for(let i=0;i<this._infos.length;i++){
             this._loadInfo(this._infos[i]).then(
                 (res)=>{
                     this._loadedAssets[i]=res;
                     this._checkLoaded();
                 },
                 (err)=>{
                     if(this._onError)this._onError(err);
                 }
             );
         }
 
     }
 
     /**
      * get asset by name or index
      */
     public getAsset(nameOrIndex:number|string):AssetInfo|null{
         if(typeof nameOrIndex==='number'){
             return this._loadedAssets[Math.round(nameOrIndex)];
         }else{
             for(let i=0;i<this._infos.length;i++){
                 const info=this._infos[i];
                 if(info.name===nameOrIndex)return this._loadedAssets[i];
             }
         }
         console.warn('no asset was found for given name/index!');
         return null;
     }
 
     //.....................................................private........................................................
 
     private _lookupTypeString(str:string):string|null{
         for(let loader of Loader.loaders){
             const key=loader.key;
             const rf=loader.prefix;
             const tf=loader.postfix;
             let filter=null;
             if(Array.isArray(str)){
                 if(!Array.isArray(tf[0]))continue;
                 for(let r of rf){
                     for(let t of tf[0]){
                         filter=r+t;
                         const reg=new RegExp(filter);
                         if(reg.test(str))return key;
                     }
                 }
             }else{
                 for(let r of rf){
                     for(let t of tf){
                         filter=r+t;
                         const reg=new RegExp(filter);
                         if(reg.test(str))return key;
                     }
                 }
             }
         }
         return null;
     }
     private _checkLoaded():boolean{
         if(this._loadedAssets.length!==this._infos.length)return false;
         for(let as of this._loadedAssets){
             if(!as)return false;
         }
         if(this._onLoaded)this._onLoaded();
         return true;
     }
     private _loadInfo(info:AssetInfo):Promise<void>{
         return new Promise((resolve,reject)=>{
             info.loader.load(info.url,
                 (res:any)=>{
                     this._progress.current[info.id]=1;
                     this._onProgress(info);
                     resolve(res);
                 },
                 (e:any)=>{
                     this._progress.current[info.id]=e.total?e.loaded/e.total:0;
                     this._onProgress(info);
                 },
                 (err:any)=>{
                     reject(err);
                 }
             );
         });
     }
     private _onProgress(info:AssetInfo):void{
         let prg=0;
         for(let i=0;i<this._progress.current.length;i++){
             prg+=this._progress.current[i];
         }
         prg=this._progress.total?prg/this._progress.total:1;
         if(this._onLoading)this._onLoading(info,prg);
     }
 
 }

 export {AssetsLoader};
 export type {AssetInfo};